// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "generator/internal/metadata_decorator_generator.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_split.h"
#include "generator/internal/codegen_utils.h"
#include "generator/internal/predicate_utils.h"
#include "generator/internal/printer.h"
#include <google/protobuf/descriptor.h>

namespace google {
namespace cloud {
namespace generator_internal {

MetadataDecoratorGenerator::MetadataDecoratorGenerator(
    google::protobuf::ServiceDescriptor const* service_descriptor,
    VarsDictionary service_vars,
    std::map<std::string, VarsDictionary> service_method_vars,
    google::protobuf::compiler::GeneratorContext* context)
    : ServiceCodeGenerator("metadata_header_path", "metadata_cc_path",
                           service_descriptor, std::move(service_vars),
                           std::move(service_method_vars), context) {}

Status MetadataDecoratorGenerator::GenerateHeader() {
  HeaderPrint(CopyrightLicenseFileHeader());
  HeaderPrint(  // clang-format off
    "// Generated by the Codegen C++ plugin.\n"
    "// If you make any local changes, they will be lost.\n"
    "// source: $proto_file_name$\n"
    "\n"
    "#ifndef $header_include_guard$\n"
    "#define $header_include_guard$\n"
    "\n");
  // clang-format on

  // includes
  HeaderLocalIncludes({vars("stub_header_path"), "google/cloud/version.h"});
  HeaderSystemIncludes(
      {HasLongrunningMethod() ? "google/longrunning/operations.grpc.pb.h" : "",
       "memory", "string"});
  HeaderPrint("\n");

  auto result = HeaderOpenNamespaces(NamespaceType::kInternal);
  if (!result.ok()) return result;

  // metadata decorator class
  HeaderPrint(  // clang-format off
    "class $metadata_class_name$ : public $stub_class_name$ {\n"
    " public:\n"
    "  ~$metadata_class_name$() override = default;\n"
    "  explicit $metadata_class_name$(std::shared_ptr<$stub_class_name$> child);\n"
    "\n");
  // clang-format on

  for (auto const& method : methods()) {
    HeaderPrintMethod(
        method,
        {MethodPattern({{IsResponseTypeEmpty,
                         // clang-format off
    "  Status $method_name$(\n",
    "  StatusOr<$response_type$> $method_name$(\n"},
   {"    grpc::ClientContext& context,\n"
    "    $request_type$ const& request) override;\n"
                         // clang-format on
                         "\n"}},
                       And(IsNonStreaming, Not(IsLongrunningOperation))),
         MethodPattern(
             {{R"""(  future<StatusOr<google::longrunning::Operation>> Async$method_name$(
      google::cloud::CompletionQueue& cq,
      std::unique_ptr<grpc::ClientContext> context,
      $request_type$ const& request) override;

)"""}},
             IsLongrunningOperation),
         MethodPattern(
             {// clang-format off
   {"  std::unique_ptr<internal::StreamingReadRpc<$response_type$>>\n"
    "    $method_name$(\n"
    "    std::unique_ptr<grpc::ClientContext> context,\n"
    "    $request_type$ const& request) override;\n"
               // clang-format on
               "\n"}},
             IsStreamingRead)},
        __FILE__, __LINE__);
  }

  for (auto const& method : async_methods()) {
    HeaderPrintMethod(
        method,
        {MethodPattern(
            {{IsResponseTypeEmpty,
              // clang-format off
    "  future<Status> Async$method_name$(\n",
    "  future<StatusOr<$response_type$>> Async$method_name$(\n"},
   {"    google::cloud::CompletionQueue& cq,\n"
    "    std::unique_ptr<grpc::ClientContext> context,\n"
    "    $request_type$ const& request) override;\n"
              // clang-format on
              "\n"}},
            And(IsNonStreaming, Not(IsLongrunningOperation)))},
        __FILE__, __LINE__);
  }

  if (HasLongrunningMethod()) {
    HeaderPrint(
        R"""(  future<StatusOr<google::longrunning::Operation>> AsyncGetOperation(
      google::cloud::CompletionQueue& cq,
      std::unique_ptr<grpc::ClientContext> context,
      google::longrunning::GetOperationRequest const& request) override;

  future<Status> AsyncCancelOperation(
      google::cloud::CompletionQueue& cq,
      std::unique_ptr<grpc::ClientContext> context,
      google::longrunning::CancelOperationRequest const& request) override;

)""");
  }

  HeaderPrint(  // clang-format off
    " private:\n"
    "  void SetMetadata(grpc::ClientContext& context,\n"
    "                   std::string const& request_params);\n"
    "  std::shared_ptr<$stub_class_name$> child_;\n"
    "  std::string api_client_header_;\n"
    "};  // $metadata_class_name$\n"
    "\n");
  // clang-format on

  HeaderCloseNamespaces();
  // close header guard
  HeaderPrint(  // clang-format off
    "#endif  // $header_include_guard$\n");
  // clang-format on
  return {};
}

Status MetadataDecoratorGenerator::GenerateCc() {
  CcPrint(CopyrightLicenseFileHeader());
  CcPrint(  // clang-format off
            "// Generated by the Codegen C++ plugin.\n"
            "// If you make any local changes, they will be lost.\n"
            "// source: $proto_file_name$\n\n");
  // clang-format on

  // includes
  CcLocalIncludes({vars("metadata_header_path"),
                   "google/cloud/internal/api_client_header.h",
                   "google/cloud/status_or.h"});
  CcSystemIncludes({vars("proto_grpc_header_path"), "memory"});
  CcPrint("\n");

  auto result = CcOpenNamespaces(NamespaceType::kInternal);
  if (!result.ok()) return result;

  // constructor
  CcPrint(  // clang-format off
    "$metadata_class_name$::$metadata_class_name$(\n"
    "    std::shared_ptr<$stub_class_name$> child)\n"
    "    : child_(std::move(child)),\n"
    "      api_client_header_(google::cloud::internal::ApiClientHeader(\"generator\")) {}\n"
    "\n");
  // clang-format on

  // metadata decorator class member methods
  for (auto const& method : methods()) {
    CcPrintMethod(
        method,
        {MethodPattern(
             {
                 {IsResponseTypeEmpty,
                  // clang-format off
    "Status\n",
    "StatusOr<$response_type$>\n"},
   {"$metadata_class_name$::$method_name$(\n"
    "    grpc::ClientContext& context,\n"
    "    $request_type$ const& request) {\n"},
   {HasRoutingHeader,
    "  SetMetadata(context, \"$method_request_param_key$=\" + request.$method_request_param_value$);\n",
    "  SetMetadata(context, {});\n"},
   {"  return child_->$method_name$(context, request);\n"
    "}\n"
    "\n",}
                 // clang-format on
             },
             And(IsNonStreaming, Not(IsLongrunningOperation))),
         MethodPattern({{HasRoutingHeader,
                         R"""(future<StatusOr<google::longrunning::Operation>>
$metadata_class_name$::Async$method_name$(
    google::cloud::CompletionQueue& cq,
    std::unique_ptr<grpc::ClientContext> context,
    $request_type$ const& request) {
  SetMetadata(*context, "$method_request_param_key$=" + request.$method_request_param_value$);
  return child_->Async$method_name$(cq, std::move(context), request);
}

)""",
                         R"""(future<StatusOr<google::longrunning::Operation>>
$metadata_class_name$::Async$method_name$(
    google::cloud::CompletionQueue& cq,
    std::unique_ptr<grpc::ClientContext> context,
    $request_type$ const& request) {
  SetMetadata(*context, {});
  return child_->$method_name$(cq, std::move(context), request);
}

)"""}},
                       IsLongrunningOperation),
         MethodPattern(
             {
                 // clang-format off
   {"std::unique_ptr<internal::StreamingReadRpc<$response_type$>>\n"
    "$metadata_class_name$::$method_name$(\n"
    "    std::unique_ptr<grpc::ClientContext> context,\n"
    "    $request_type$ const& request) {\n"},
   {HasRoutingHeader,
    "  SetMetadata(*context, \"$method_request_param_key$=\" + request.$method_request_param_value$);\n",
    "  SetMetadata(*context, {});\n"},
   {"  return child_->$method_name$(std::move(context), request);\n"
    "}\n"
    "\n",}
                 // clang-format on
             },
             IsStreamingRead)},
        __FILE__, __LINE__);
  }

  for (auto const& method : async_methods()) {
    CcPrintMethod(
        method,
        {MethodPattern(
            {{IsResponseTypeEmpty,
              // clang-format off
    "future<Status>\n",
    "future<StatusOr<$response_type$>>\n"},
    {R"""($metadata_class_name$::Async$method_name$(
    google::cloud::CompletionQueue& cq,
    std::unique_ptr<grpc::ClientContext> context,
    $request_type$ const& request) {)"""},
   {HasRoutingHeader, R"""(
  SetMetadata(*context, "$method_request_param_key$=" + request.$method_request_param_value$);)""",
   R"""(
  SetMetadata(*context, {});)"""}, {R"""(
  return child_->Async$method_name$(cq, std::move(context), request);
}

)"""}},
            // clang-format on
            And(IsNonStreaming, Not(IsLongrunningOperation)))},
        __FILE__, __LINE__);
  }

  // long running operation support methods
  if (HasLongrunningMethod()) {
    CcPrint(R"""(future<StatusOr<google::longrunning::Operation>>
$metadata_class_name$::AsyncGetOperation(
    google::cloud::CompletionQueue& cq,
    std::unique_ptr<grpc::ClientContext> context,
    google::longrunning::GetOperationRequest const& request) {
  SetMetadata(*context, "name=" + request.name());
  return child_->AsyncGetOperation(cq, std::move(context), request);
}

future<Status> $metadata_class_name$::AsyncCancelOperation(
    google::cloud::CompletionQueue& cq,
    std::unique_ptr<grpc::ClientContext> context,
    google::longrunning::CancelOperationRequest const& request) {
  SetMetadata(*context, "name=" + request.name());
  return child_->AsyncCancelOperation(cq, std::move(context), request);
}

)""");
  }

  CcPrint(  // clang-format off
    "void $metadata_class_name$::SetMetadata(grpc::ClientContext& context,\n"
    "                                        std::string const& "
    "request_params) {\n"
    "  context.AddMetadata(\"x-goog-request-params\", request_params);\n"
    "  context.AddMetadata(\"x-goog-api-client\", api_client_header_);\n"
    "}\n\n"
            // clang-format on
  );

  CcCloseNamespaces();
  return {};
}

}  // namespace generator_internal
}  // namespace cloud
}  // namespace google
